package com.chinda.common.init;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.chinda.common.constants.CommonConstant;
import com.chinda.common.enums.MenuTypeEnum;
import com.chinda.modules.sys.entity.SysMenuEntity;
import com.chinda.modules.sys.service.SysMenuService;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author Wang Chinda
 * @date 2021/7/4
 * @see
 * @since 1.0
 */
@Slf4j
@Component
public class InitMenuRunner implements ApplicationRunner, ApplicationContextAware {

    @Value("${spring.application.cname}")
    private String applicationCname;
    private ApplicationContext applicationContext;

    private final SysMenuService sysMenuService;
    public InitMenuRunner(SysMenuService sysMenuService) {
        this.sysMenuService = sysMenuService;
    }

    /**
     * 启动ioc时, 传递运行参数--initMenu=true时会初始化菜单, 默认false
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        if (args.containsOption("initMenu")) {
            log.info("初始化菜单, 应用中文名:{}", applicationCname);
            // 1. 清空菜单表
            sysMenuService.truncateMenu();
            // 2. 初始化根目录
            SysMenuEntity root = new SysMenuEntity();
            root.setParentId(0L);
            root.setName(applicationCname);
            root.setType(MenuTypeEnum.CATALOGUE);
            root.setIcon(CommonConstant.DEFAULT_ICON);
            root.setSort(0);
            sysMenuService.save(root);

            // 3. 获取handlerMapping的Bean节点, 进而获取所有方法的mapping
            RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
            Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = handlerMapping.getHandlerMethods();

            // 4. 初始化子菜单, 细粒度授权规则可在此方法实现 4.1) 获取所有被@Api修饰的bean 4.2) 根据@Api修饰的bean名称去重 4.3) 保存子菜单
            String servletContextPath = StrUtil.nullToEmpty(applicationContext.getEnvironment().getProperty("server.servlet.context-path"));
            AtomicInteger sort = new AtomicInteger();
            List<SysMenuEntity> sysMenuList = handlerMethodMap.values()
                    .parallelStream()
                    .filter(h -> h.getMethod().getDeclaringClass().getAnnotation(Api.class) != null)
                    .collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(h -> Convert.toStr(h.getBean())))), ArrayList::new))
                    .parallelStream()
                    .map(h -> {
                        SysMenuEntity sysMenu = new SysMenuEntity();
                        sysMenu.setParentId(root.getMenuId());

                        String[] tags = h.getMethod().getDeclaringClass().getAnnotation(Api.class).tags();
                        if (ArrayUtil.isNotEmpty(tags) && StrUtil.isNotBlank(tags[0])) {
                            sysMenu.setName(tags[0]);
                        } else {
                            log.warn("初始化菜单时, 扫描到匿名菜单, 请检查[{}]中@Api注解", StrUtil.subBefore(h.getMethod().getDeclaringClass().getName(), "$$", false));
                            sysMenu.setName(CommonConstant.ANONYMOUS);
                        }
                        String[] values = h.getMethod().getDeclaringClass().getAnnotation(RequestMapping.class).value();

                        sysMenu.setUrl(StrUtil.format("{}{}", servletContextPath, StrUtil.emptyIfNull(values[0])));
                        sysMenu.setType(MenuTypeEnum.MENU);
                        sysMenu.setIcon(CommonConstant.DEFAULT_ICON);
                        sysMenu.setSort(sort.getAndIncrement());
                        return sysMenu;
                    })
                    .collect(Collectors.toList());
            sysMenuService.saveBatch(sysMenuList);
            log.info("初始化菜单完成,  根目录为: {}, 子菜单: {}", applicationCname, sysMenuList.stream().map(SysMenuEntity::getName).collect(Collectors.joining(StrUtil.COMMA)));
        }
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
